package com.alameda.common.aop;

import com.alameda.common.annotation.UnRpcIdempotent;
import com.alameda.common.commonpent.redis.api.RedisCacheTemplate;
import com.alameda.common.exception.BusinessException;
import com.alameda.common.utils.PubUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.dubbo.rpc.RpcContext;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.MDC;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import static com.alameda.common.base.constant.ProjectConstant.RpcConstant.CONSUMER;
import static com.alameda.common.base.constant.ProjectConstant.RpcConstant.PROVIDER;
import static com.alameda.common.base.constant.ProjectConstant.TRACE_ID;

/**
 * 分布式幂等aop
 *
 * @author Lv
 * @date 2024/7/9
 */
@Slf4j
@Aspect
@Component
public class RpcIdempotentAop {
    /**
     * 定义切点 扫描所有ProviderImpl方法
     */
    @Pointcut("within(com.alameda.provider..*ProviderImpl)")
    public void point() {
    }

    @Autowired
    private RedisCacheTemplate redisCacheTemplate;

    /**
     * 后置处理
     *
     * @param proceedingJoinPoint 切点对象
     */
    @Around("point()")
    public Object around(ProceedingJoinPoint proceedingJoinPoint) {
        MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
        Method method = signature.getMethod();
        String methodName = method.getName();

        if (PubUtils.isEmpty(MDC.get(TRACE_ID))) {
            MDC.put(TRACE_ID, UUID.randomUUID().toString().replace("-", ""));
        }

        /// log.info(":{} 无需幂等校验 :{}", methodName, method.isAnnotationPresent(UnRpcIdempotent.class));
        return method.isAnnotationPresent(UnRpcIdempotent.class)
                ? dealRpcMethod(proceedingJoinPoint, methodName)
                : dealIdempotentRpcMethod(proceedingJoinPoint, methodName);
    }

    /**
     * 处理幂等场景rpc调用
     *
     * @param proceedingJoinPoint 切点对象
     * @param methodName          调用方法名称
     * @return
     */
    private Object dealIdempotentRpcMethod(ProceedingJoinPoint proceedingJoinPoint, String methodName) {
        // 获取当前请求链路id
        String traceId = MDC.get(TRACE_ID);

        RpcContext rpcContext = RpcContext.getContext();
        // 设定分布式调度key
        String redisKey = rpcContext.isConsumerSide() ? CONSUMER + traceId : PROVIDER + traceId;

        String value = redisCacheTemplate.getCacheObject(redisKey);
        Object proceed = null;
        if (PubUtils.isEmpty(value)) {
            try {
                // 执行方法
                proceed = proceedingJoinPoint.proceed();
            } catch (Throwable throwable) {
                log.error("RPC调用过程报错", throwable);
                BusinessException.throwException(throwable);
            } finally {
                log.info("完成RPC[{}]方法调用", methodName);
                value = redisKey;
                // 写入redis
                redisCacheTemplate.setCacheObject(redisKey, value, 3, TimeUnit.SECONDS);
                // 释放MDC内存
                MDC.remove(TRACE_ID);
            }
        } else {
            log.info("重复请求rpc接口[{}]", methodName);
        }
        return proceed;
    }

    /**
     * 处理rpc方法调用 无需关注幂等问题
     *
     * @param proceedingJoinPoint 切点对象
     * @param methodName          切点对象
     * @return
     */
    private Object dealRpcMethod(ProceedingJoinPoint proceedingJoinPoint, String methodName) {
        Object proceed = null;
        try {
            // 执行方法
            proceed = proceedingJoinPoint.proceed();
        } catch (Throwable throwable) {
            log.error("RPC调用过程报错", throwable);
            BusinessException.throwException(throwable);
        } finally {
            log.info("完成RPC[{}]方法调用", methodName);
            // 释放MDC内存
            MDC.remove(TRACE_ID);
        }
        return proceed;
    }
}
